//SetTrainingPattern
//
//NOTE TO ANY PROGRAMMERS: This class has become horrible and bloated beyond all
//description. This has to do with time limits imposed on me. This should be
//considered a draft as the next version will likely split SetTrainingPattern
//into 2 or even 3 different classes. Additionally the methods will also be 
//cleaned up, as they too lack efficiency from the hodge-podge "quick and dirty"
// way and circumstances under which they were programmed. My apologies for the 
//lack of readability before this new version is implemented.
//
//This class takes in a grammar corpus composed of a square matrix with
//completely orthogonal rows and columns and arranges those word tokens
//grammatically. Many functions are meant specifically to work with the default
//Corpus.dat which should be included with this class (if not a 25x25 identity 
//matrix can be substituted in its place), however many of the functions are 
//general enough to support a corpus with any number of words and adjustment
//of numeric parameters after the point indicated below should suffice should
//the user want to user a different corpus file (assuming its properly
//formated). If the corpus has a different set of grammar rules or must have
//a radically different format, it goes without saying that you probably 
//shouldn't be using this class. This class reads the corpus from a standard
//text file. It then uses the Jama Matrix Package to print out the Sentence 
//set created (in the same form as the corpus) to a text file. It also prints
//out an english translation of the finished sentence set to allow for checking
//of its grammar.
//
//Zach Tosi Feb 9 2011
//
//Special thanks to Dr. David Noelle at UC Merced who inspired the structure of
//several functions, and to the writers of the JAMA matrix package.
package utils.corpus_generator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Random;
import java.util.Vector;

import Jama.Matrix;

public class SetTrainingPattern {

	public double [][] Corpus;
	protected Pattern Sset, Probs, Sent, Psent;
	private Random randi;
	private int length;
	public int numWho = 0;
	private int numNouns = 10;
	private int verbSing = 6;
	private int verbPlur = 6;
	private int whiteRabbit = 0;
	private Vector<Boolean> bl = new Vector<Boolean>();
	private Vector<Integer> Diff = new Vector<Integer>();
	boolean V;
	
	//Constructor reads from the corpus file, sets the length for the 
	//token vectors stored in Pattern Sset, initializes randi the 
	//random number generator
	public SetTrainingPattern(String CorpFile){
		if(read(CorpFile)){
			Sset = new Pattern(length);
			Probs = new Pattern(length);
			Sent = new Pattern(length);
			Psent = new Pattern(length);
			randi = new Random();
		}else{
			System.err.println("Something went wrong.");
		}
	}
	
	public SetTrainingPattern(String CorpFile, boolean V){
		this(CorpFile);
		Sset.MakeVerbInds();
		this.V = V;
	}
	
	/*Takes in the filename and uses the Jama Matrix Package's read function
	//to read the corpus as a matrix. This is then stored in the 2D Array 
	//Corpus to allow easier access to each individual row. Jama lacked a 
	//function to access whole rows unless one first accessed the underlying 
	//2D array anyway. The column length is then extracted and set to be the 
	//default pattern's token length. This attempts to accommodate for tokens
	//of any vector length. Returns true if all went well, false if there was
	//a problem and catches errors if there are any. The structure is loosely 
	//based on Dr. David Noelle's read functions in his Introduction to AI
	//programming assignments. */
	public boolean read(String Filename){
		try{
			File patsFile = new File(Filename);
			if(patsFile.exists() && patsFile.canRead()){
				FileInputStream patsFileIn = new FileInputStream(patsFile);
				InputStreamReader patsISReader
					= new InputStreamReader(patsFileIn);
				BufferedReader patsBufferedReader
					= new BufferedReader(patsISReader);
				Corpus = Matrix.read(patsBufferedReader).getArray();
				length = Corpus[0].length;
				 patsBufferedReader.close();
				return true;
			} else{
				return false;
			}
		}catch(IOException e){
			return false;
		}
	}
	
	//Quick accessor to get Rows from corpus
	public double [] Get(int index){
		return Corpus[index];
	}
	
	public void ADD(double [] P){
		//if(Probs.getRowDim() < Sset.getRowDim()-1){
			//System.out.println("Probs: " + Probs.getRowDim() + " Sset: " + Sset.getRowDim());
			
			//throw new IllegalArgumentException("Missed a word...");
		//}
		
		
		
		Probs.Add(P);
		Psent.Add(P);
	}
	
	public void TAdd(double [] S){
		
		Sent.Add(S);
		Sset.Add(S);
		Diff.add(Sset.getRowDim()-Probs.getRowDim());
		
		if(Psent.getRowDim() == Sent.getRowDim()-1 && Psent.getRowDim() != 0){
			//System.out.println("I happen");
			for(int k = 0; k < length; k++){
				if(Sent.GetLast()[k] > 0.0 && Psent.GetLast()[k] <= 0.0){
					Sent.Translate("Example.dat");
					System.out.println(Psent.getRowDim());
					System.out.println("Probs: " + Probs.getRowDim() + " Sset: " + Sset.getRowDim());
					Sent.Print("Sentence");
					Psent.Print("Psent");
					throw new IllegalArgumentException ("Made a mistake");
				}
			}
		
	}
	
	}
	
	public double [] AddArray(double [] ar1, double [] ar2){
		double [] P = new double[length];
		for(int i = 0; i < length; i++){
			P[i] = ar1[i] + ar2[i];
		}		
	
		return P;
	}
	
	public double [] NounOrVerb(int Nindex, boolean addNow){
		double [] NOV = AddArray(VerbProb(Nindex, false), AnyNounP(false));
		for(int i = 0; i < NOV.length; i++){
			NOV[i] = NOV[i]/2.0;
		}
		if(addNow){
			ADD(NOV);
		}
		return NOV;
	}
	
	public double [] AnyNounP(boolean Addnow){
		double [] P = new double[length];
		for(int i = 0; i < numNouns; i++){
			P[i] = 1.0/numNouns;
		}
		if(Addnow){
			ADD(P);		
		}
		return P;
	}
	
	public double [] VerbProb(int Nindex, boolean addNow){
		double [] P = new double [length];
		int V = 0;
		int start = 0;
		if(NGroup(Nindex) == 1){
			start = 16;
			V = verbPlur;
		}else{
			start = 10;
			V = verbSing;
		}
		for(int i = start; i < start+V; i++)
			P[i] = 1.0/V;
		
		if(addNow)
			ADD(P);
		
		return P;
	}
	
	public double [] VerbReplace(int Nindex, double [] Prob){
		double [] Pr = null;
		
		if(NGroup(Nindex) != VGroup(Prob)){
			//System.out.println("Verb Replace Happened! " + "Probs " + 
			//		Probs.getRowDim() + " Sent " + Sset.getRowDim());
			 Pr = VerbProb(Nindex, false);
			double sum = 0.0;
			for(int i = 0; i < length; i++){
				if(i < 10 || i > 21){
					sum += Prob[i];
				}
			}
			sum = 1.0 - sum;
			for(int i = 0; i < length; i++){
				if(i < 10 || i > 21)
					Pr[i] = Prob[i];
				else
					Pr[i] = Pr[i]*sum;
			}
			Probs.SetLast(Pr);
			Psent.SetLast(Pr);
		}
		
		return Pr;
	}

	
	public double whoProb(int NumWhos, boolean flip){
		double odds;
		switch(NumWhos){
		case 0: odds = 0.75; break;
		case 1: odds = 0.5625; break;
		case 2: odds = 0.375; break;
		case 3: odds = 0.1875; break;
		case 4: odds = 0.0; break;
		default: odds = 0.0; break;
		}
		if(flip){
			odds = odds/2.0;
		}
			return odds;
	}
	
	public void WhoPossible(int Nindex, int whoCount, boolean flip,
			boolean nouncase, boolean Case2){
		double [] P = new double [length];
		double who = whoProb(whoCount, flip);
		double whoComp = 1.0-who;
	
		if(nouncase){
			P = NounOrVerb(Nindex, false);
		}else{
			P = VerbProb(Nindex, false);
				
		}
		
			
		
			
		for(int i = 0; i < length; i++){
			P[i] = P[i] * whoComp;
		}
		P[22] = who;
		
		
		
		/**double C = 0.0;
		for(int i = 0; i < length; i++){
			C += P[i];
		}
		if(C != 1.0){
			System.out.println("Who: " + who);
			System.out.println("Tot" + C);
			System.out.println("NumWho: " + numWho);
			throw new IllegalArgumentException("Not summing to 1.0");
		}
		**/
		ADD(P);
		
		if(Case2){
			VerbP(Nindex, false);
		}
	}
	
	public void Period(boolean noun, int numWho, boolean flip){
		double [] P = new double[length];
		double who = whoProb(numWho, flip);
		P[22] = who;
		P[23] = 1-who;
		if(noun){
			P[23] = 0.5;
			for(int i = 0; i < numNouns; i++){
				P[i] = 0.5/numNouns;
			}
		}
		
		ADD(P);
	}

	/*/All functions past the line below are
	//Corpus.dat SPECIFIC functions (these will have to be altered in 
	//in accordance with the parameters of the particular corpus if the 
	//default corpus is not being used). However if the the corpus has a
	//similar enough format to the default alterations to the numerical 
	//parameter values and switch statements should suffice.
	//_______________________________________________________________________*/
	
	/*/Given the noun's row number in the corpus (hereafter refered to as
	// "index") returns 1 if the noun is singular (thus requiring singular
	//verb conjugation) or 2 if plural. Throws an error if the index does not
	//correspond to the index range of nouns in Corpus.dat*/
	public int NGroup(int Nindex){
		if(Nindex < 6 && Nindex >= 0){
			return 1;
		}else if(Nindex > 5 && Nindex <10){
			return 2;
		}else{
			throw new IllegalArgumentException("Not a noun index");
		}
	}
	
	public int VGroup(double [] Pr){
		if(Pr[10] != 0.0){
			return 2;
		}
		else if (Pr[17] != 0.0){
			return 1;
		}else
			throw new IllegalArgumentException("No verb predicted or " +
					"inidices are off");
	}
	
	/*/Creates recursive clauses in the context of Elman 1991's grammar rules
	//takes in the recursion count and does nothing if the sentence has used its
	//maximum number of recursive clauses (determined probabilistically in 
	//write() in the range of 0-4. Also takes in the index of the noun 
	//immediately preceding the RC to preserve verb agreement. Returns
	//a recursive count to ensure max RecCount does not exceed allotment in 
	//write()*/
	public int recursiveClause(int RecCount, int Nindex, boolean periods){
		if(RecCount > 0){//Else do nothing
	
		//RCount was > 0 ergo this will be a recursive clause, subtract from
		//the pre-established allotment
		RecCount--;
		
		//Type of recursive clause...
		double flip = randi.nextDouble();
		
		//Add "who" to pattern to demarcate beginning of RC
		TAdd(Get(22));
	
		numWho++;
				
		NounOrVerb(Nindex, true);
		
		//If heads RC is of form: who VP(NP)
		if(flip < 0.5){
			bl.add(false);
			//Updates RecCount if Verb included a recursive clause
			RecCount = Verb(Nindex, RecCount, periods, true);
		
			
			
			//If tails RC is of form: who NP VP
		}else{
			whiteRabbit++;
				//write new noun, n1 is our noun index
				int n1 = NounP();
				
				//flip again! 
				flip = randi.nextDouble();
				
				WhoPossible(n1, numWho, true, false, true);
				//if tails...
				if(flip > 0.50 && RecCount > 0){
				bl.add(true);
					//Updates RecCount because our NP was another RC!
					RecCount = recursiveClause(RecCount, n1, periods);
					
					if(bl.lastElement())
						VerbProb(n1, true);
					
					bl.remove(bl.lastElement());
				
					
				}

				//Verb agrees with and encapsulates n1. VerbP does not produce
				//verbs which preclude DOs, and for the others excludes DOs as
				//the recursive nouns are the direct object
				VerbP(n1, true);
				
				if(whiteRabbit == 1 && !periods){
					VerbProb(Nindex, true);
					//System.out.println("White Rabbit 1 occurs!" + Sset.getRowDim());
				}else if(whiteRabbit == 1 && periods){
					Period(false, 4, false);
					//System.out.println("White Rabbit 2 occurs!" + Sset.getRowDim());
				}
				whiteRabbit--;
		}
		
		}
		return RecCount;
	}
	
	/*/Selects a random noun, if noun is singular and non-proper it attaches
	//"the" before it. Returns index of randomly selected noun*/
	public int NounP(){
		int N = randi.nextInt(10);
		//if(N < 4){
			//"the"
		//	TAdd(Get(23));
		//}
		TAdd(Get(N));
		return N;
	}
	
	/*Verb takes in the index of the noun its being called for and generates
	//a randomly selected and conjugated Verb phrase. If the verb requires a
	//direct object, that object is also generated. Verb also takes in 
	//RecCount as it must keep track of this count in case a direct object
	//noun phrase is a RC. Whether or not the DO is a RC and whether or not
	//a DO optional verb has a DO is determined probabilistically. */
	public int Verb(int Nindex, int RecCount, boolean Periods, boolean rec){
		
		
		//Which group of verbs (DO necessary, optional, or none)?
		int Vgroup = randi.nextInt(3);
		int Vob = 2*(Vgroup);
		//Which verb in the group?
		int V = Vob + (randi.nextInt(2));
		
		if(NGroup(Nindex) == 1){
			//Shift Verb index to singular verb range
			V = V + 16;
		}else if(NGroup(Nindex) == 2){
			//Shift Verb index to plural verb range
			V = V + 10;
		}else{
			throw new IllegalArgumentException("Not a noun index");
		}
		
		TAdd(Get(V));
		
		//Flip a coin!
		double flip = randi.nextDouble();
		
		//Requires a direct object
		if(Vgroup == 0){
			AnyNounP(true);
			//Direct Object (DO)
			int noun = NounP();
			//If heads and we have more RCs to spare...
			if(!Periods || whiteRabbit >= 1)
				WhoPossible(Nindex, numWho, true, false, false);
			else
				Period(false, numWho, true);
			if(flip > 0.5 && RecCount > 0){
				if(rec)
					bl.remove(bl.lastElement());
				//Phrase is recursive and updates RecCount
				RecCount = recursiveClause(RecCount, noun, Periods);
			}
			
		} 
		//Direct Object optional
		if(Vgroup == 1){
				//If heads...
			if(!Periods || whiteRabbit >= 1)
				NounOrVerb(Nindex, true);
			else
				Period(true, 4, true);
			
			if(flip > 0.5){
				//Verb gets a DO!
				int noun = NounP();
				//Flip again...
				if(!Periods || whiteRabbit >= 1)
					WhoPossible(Nindex, numWho, true, false, false);
				else
					Period(false, numWho, true);
				flip = randi.nextDouble();
				//If heads and we have RCs to spare...
				if(flip > 0.5 && RecCount > 0){
					if(rec)
						bl.remove(bl.lastElement());
					//DO is recursive! Update RecCount.
					RecCount = recursiveClause(RecCount, noun, Periods);
				}
				
					
			}
		}
		if(Vgroup == 2){
			if(!Periods || whiteRabbit >= 1)
				VerbProb(Nindex, true);
			else
				Period(false, 4, false);
			
		}
		
		
		return RecCount;
	}
	
	/*/VerbP takes in a noun index and adds a conjugated verb to the set
	//However, unlike Verb(), Verb P only allows verbs with direct objects
	//or DO optional and does not generate the DO. VerbP() is specific to
	//recursive cases in which the preceding noun counts as the DO and ergo
	//the verb must at least optionally allow a DO, but must not write it 
	//afterwards.*/
	public void VerbP(int Nindex, boolean Now){
		if(Probs.GetLast()[10] != 0.0 || Probs.GetLast()[17] != 0.0)
			VerbReplace(Nindex, Probs.GetLast());
		double [] VP = Probs.GetLast().clone();
		VP[14] = 0.0;
		VP[15] = 0.0;
		VP[20] = 0.0;
		VP[21] = 0.0;
		double sum = 0.0;
		for(int i = 0; i < 22; i++){
			sum += VP[i];
		}
	
		for(int i = 0; i < 22; i++){
			VP[i] = (VP[i]/sum)* 
				(1-VP[22]);
		}
		Probs.SetLast(VP);
		Psent.SetLast(VP);
		if(Now){
		//Group 1 or 2? (DO req. or DO opt.)
		int Vgroup = randi.nextInt(2);
		int Vob = 2*(Vgroup);
		//which V in group?
		int V = Vob + (randi.nextInt(2));
		if(NGroup(Nindex) == 1){
			//Shift index to singular verb range
			V = V + 16;
		}else if(NGroup(Nindex) == 2){
			//Shift index to plural verb range
			V = V + 10;
		}else{
			throw new IllegalArgumentException("Not a noun index");
		}
		TAdd(Get(V));
		}

	}
	
	//Write() as the name implies utilizes all the preceding functions to
	//arrange the corpus tokens into a list of numSentences grammatically correct
	//sentences.
	public double [][] write(int numSentences){
		if(Sset.getLength() != length){
			throw new IllegalArgumentException("Something went wrong, " +
					"Pattern matricies and the corpus should have the same " +
					"column length.");
		}
		int recursiveCount;
		for(int i = 0; i < numSentences; i++){
			//Roll a d-100, the number determines the max # of RCs allowed 
			double NRec = randi.nextDouble();
			//25% of no RCs see: Tong et al 2007
			if(NRec <= 0.25){
				recursiveCount = 0;
			}else if(NRec <= 0.4375 && NRec > 0.25){
				recursiveCount = 1;
			}else if(NRec <= 0.625 && NRec > 0.4375){
				recursiveCount = 2;
			}else if(NRec <= 0.8125 && NRec > 0.625){
				recursiveCount = 3;
			}else{
				recursiveCount = 4;
			}
			//Starting NP
			int N = NounP();
			if(V)
				Sset.AddNounInd();
			
			WhoPossible(N, 0, false, false, false);
			//Is it recursive? If recursiveCount is 0, does nothing.
			recursiveCount = recursiveClause(recursiveCount, N, false);
			bl.clear();
			
			/**
			 * VERB REPLACE
			 */
			VerbReplace(N, Probs.GetLast());
			Probs.replace = true;
			
			if(V)
				Sset.AddVerb();
			
			//VP
			Verb(N, recursiveCount, true, false);
			//Period 
			TAdd(Get(23));
			
			AnyNounP(true);
			
			bl.clear();
			numWho = 0;
			
			for(int w = 0; w < Sent.getRowDim()-1; w++){
				for(int k = 0; k < Sent.getLength(); k++){
					if(Sent.get(w+1, k) > 0.0 && Psent.get(w, k) <= 0.0){
						Sent.Translate("Example.dat");
						System.out.println(w);
						System.out.println("Probs: " + Probs.getRowDim() + " Sset: " + Sset.getRowDim());
						Sent.Print("Sentence");
						Psent.Print("Psent");
						throw new IllegalArgumentException ("Made a mistake");
					}
				}
			}
			
			if(Sset.getRowDim() > Probs.getRowDim()){
				Sent.Translate("Example.txt");
				System.out.println("Probs: " + Probs.getRowDim() + " Sset: " + Sset.getRowDim());
				for(Integer I : Diff){
					System.out.print(I + " ");
				}
				Sent.Print("Sentence.txt");
				Psent.Print("Psent.txt");
				throw new IllegalArgumentException("Sent was larger");
			}
			Sent.Clear();
			Psent.Clear();
			Diff.removeAllElements();
			Probs.replace = false;
		}
		return Sset.Get2DArray();		
	}
	
	/*/Prints the pattern stored in Sset as a list of its constituent vectors
	//to the file specified. Prints a translation into English of the pattern
	//in Sset to allow checking of its grammar. Uses the Jama matrix package
	//print function for the former operation, and printwriter + a switch 
	//statement for the latter. Also prints out the average number of words
	//per sentence to the console. */
	public void PrintPattern(String Filename){
		System.out.println(Sset.getRowDim());
		try{
		File Output = new File(Filename);
		FileOutputStream FOS = new FileOutputStream(Output);
		PrintWriter Mat = new PrintWriter(FOS); 
		@SuppressWarnings("unused")
		Matrix TrSet = Sset.createMatrix();
		
		Mat.close();
		
		
		}catch(IOException e){
			System.err.print("Error: " + e.getMessage());
		}
	}
	
	
	public double [][] readPat(String Filename){
		double [][] pattern = null;
		try{
			File patsFile = new File(Filename);
			
				FileInputStream patsFileIn = new FileInputStream(patsFile);
				InputStreamReader patsISReader
					= new InputStreamReader(patsFileIn);
				BufferedReader patsBufferedReader
					= new BufferedReader(patsISReader);
				pattern = Matrix.read(patsBufferedReader).getArray();
				patsBufferedReader.close();
				
		}catch(IOException e){
			System.err.println(e);
		}
		return pattern;
	}
	
	public Vector<int []> GetVerbDists(){
		return Sset.GetVerbs();
	}
	
	public Pattern GetProbs(){
		return Probs;
	}
	
}
